/*******************************************************************************
** Qt Advanced Docking System
** Copyright (C) 2017 Uwe Kindler
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation; either
** version 2.1 of the License, or (at your option) any later version.
**
** This library is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public
** License along with this library; If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/


//============================================================================
/// \file   ads_globals.cpp
/// \author Uwe Kindler
/// \date   24.02.2017
/// \brief  Implementation of
//============================================================================


//============================================================================
//                                   INCLUDES
//============================================================================
#include <QVariant>
#include <QPainter>
#include <QAbstractButton>
#include <QStyle>

#include "DockSplitter.h"
#include "DockManager.h"
#include "IconProvider.h"
#include "ads_globals.h"

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
#include <QSettings>
#include <QFile>
#include <QApplication>
#include <qpa/qplatformnativeinterface.h>
#endif

namespace ads
{

namespace internal
{
const int FloatingWidgetDragStartEvent = QEvent::registerEventType();
const int DockedWidgetDragStartEvent = QEvent::registerEventType();
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
// 定义全局变量
static QString _window_manager;
static QHash<QString, xcb_atom_t> _xcb_atom_cache;

// 判断是否为X11平台
bool is_platform_x11()
{
    return QGuiApplication::platformName() == QLatin1String("xcb");
}

// 获取X11连接
xcb_connection_t* x11_connection()
{
    if (!qApp)
        return nullptr;
    QPlatformNativeInterface *native = qApp->platformNativeInterface();
    if (!native)
        return nullptr;
    void *connection = native->nativeResourceForIntegration(QByteArray("connection"));
    return reinterpret_cast<xcb_connection_t *>(connection);
}

// 获取X11原子
xcb_atom_t xcb_get_atom(const char *name)
{
    if (!is_platform_x11())
    {
        return XCB_ATOM_NONE;
    }
    auto key = QString(name);
    if(_xcb_atom_cache.contains(key))
    {
        return _xcb_atom_cache[key];
    }
    xcb_connection_t *connection = x11_connection();
    xcb_intern_atom_cookie_t request = xcb_intern_atom(connection, 1, strlen(name), name);
    xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, request, NULL);
    if (!reply)
    {
        return XCB_ATOM_NONE;
    }
    xcb_atom_t atom = reply->atom;
    if(atom == XCB_ATOM_NONE)
    {
        ADS_PRINT("Unknown Atom response from XServer: " << name);
    }
    else
    {
        _xcb_atom_cache.insert(key, atom);
    }
    free(reply);
    return atom;
}

// 更新X11属性
void xcb_update_prop(bool set, WId window, const char *type, const char *prop, const char *prop2)
{
    auto connection = x11_connection();
    xcb_atom_t type_atom = xcb_get_atom(type);
    xcb_atom_t prop_atom = xcb_get_atom(prop);
    xcb_client_message_event_t event;
    event.response_type = XCB_CLIENT_MESSAGE;
    event.format = 32;
    event.sequence = 0;
    event.window = window;
    event.type = type_atom;
    event.data.data32[0] = set ? 1 : 0;
    event.data.data32[1] = prop_atom;
    event.data.data32[2] = prop2 ? xcb_get_atom(prop2) : 0;
    event.data.data32[3] = 0;
    event.data.data32[4] = 0;
    xcb_send_event(connection, 0, window,
                   XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_PROPERTY_CHANGE,
                   (const char *)&event);
    xcb_flush(connection);
}


//============================================================================
// 获取属性的回复结构体指针
xcb_get_property_reply_t* _xcb_get_props(WId window, const char *type, unsigned int atom_type)
{
    // 如果不是X11平台，则返回空指针
    if (!is_platform_x11())
    {
        return nullptr;
    }
    
    // 获取X11连接
    xcb_connection_t *connection = x11_connection();
    
    // 获取类型的原子
    xcb_atom_t type_atom = xcb_get_atom(type);
    
    // 如果类型原子不存在，则返回空指针
    if (type_atom == XCB_ATOM_NONE)
    {
        return nullptr;
    }
    
    // 发送获取属性的请求
    xcb_get_property_cookie_t request = xcb_get_property_unchecked(connection, 0, window, type_atom, atom_type, 0, 1024);
    
    // 获取属性的回复
    xcb_get_property_reply_t *reply = xcb_get_property_reply(connection, request, nullptr);
    
    // 如果回复存在且类型不匹配，则打印错误信息，并释放回复结构体，并返回空指针
    if(reply && reply->type != atom_type)
    {
        ADS_PRINT("ATOM TYPE MISMATCH (" << type <<"). Expected: " << atom_type << "  but got " << reply->type);
        free(reply);
        return nullptr;
    }
    
    return reply;
}
//============================================================================

// 模板函数，用于获取属性列表
template <typename T>
void xcb_get_prop_list(WId window, const char *type, QVector<T> &ret, unsigned int atom_type)
{
    // 获取属性的回复
    xcb_get_property_reply_t *reply = _xcb_get_props(window, type, atom_type);
    
    // 如果回复存在且格式为32位且类型匹配且值长度大于0
    if (reply && reply->format == 32 && reply->type == atom_type && reply->value_len > 0)
    {
        // 获取属性值的指针
        const xcb_atom_t *data = static_cast<const T *>(xcb_get_property_value(reply));
        
        // 调整返回值的大小
        ret.resize(reply->value_len);
        
        // 复制属性值到返回值中
        memcpy((void *)&ret.first(), (void *)data, reply->value_len * sizeof(T));
    }
    
    // 释放回复结构体
    free(reply);
}
//============================================================================

// 获取字符串类型的属性
QString xcb_get_prop_string(WId window, const char *type)
{
    QString ret;
    
    // 首先尝试使用UTF8编码
    xcb_atom_t utf_atom = xcb_get_atom("UTF8_STRING");
    
    // 如果UTF8编码存在
    if(utf_atom != XCB_ATOM_NONE)
    {
        // 获取属性的回复
        xcb_get_property_reply_t *reply = _xcb_get_props(window, type, utf_atom);
        
        // 如果回复存在且格式为8位且类型为UTF8编码
        if (reply && reply->format == 8 && reply->type == utf_atom)
        {
            // 获取属性值的指针
            const char *value = reinterpret_cast<const char *>(xcb_get_property_value(reply));
            
            // 将属性值转换为QString，并设置长度
            ret = QString::fromUtf8(value, xcb_get_property_value_length(reply));
            
            // 释放回复结构体
            free(reply);
            
            return ret;
        }
        
        // 释放回复结构体
        free(reply);
    }
    
    // 如果没有使用UTF8编码，则尝试使用XCB_ATOM_STRING编码
    xcb_get_property_reply_t *reply = _xcb_get_props(window, type, XCB_ATOM_STRING);
    
    // 如果回复存在且格式为8位且类型为XCB_ATOM_STRING编码
    if (reply && reply->format == 8 && reply->type == XCB_ATOM_STRING)
    {
        // 获取属性值的指针
        const char *value = reinterpret_cast<const char *>(xcb_get_property_value(reply));
        
        // 将属性值转换为QString，并设置长度
        ret = QString::fromLatin1(value, xcb_get_property_value_length(reply));
    }
    
    // 释放回复结构体
    free(reply);
    
    return ret;
}
//============================================================================

// 打印属性列表
bool xcb_dump_props(WId window, const char *type)
{
    QVector<xcb_atom_t> atoms;
    
    // 获取属性列表
    xcb_get_prop_list(window, type, atoms, XCB_ATOM_ATOM);
    
    qDebug() << "\n\n!!!" << type << "  -  " << atoms.length();
    
    xcb_connection_t *connection = x11_connection();
    
    // 遍历属性列表，获取每个属性的名称并打印
    for (auto atom : atoms)
    {
        auto foo = xcb_get_atom_name(connection, atom);
        auto bar = xcb_get_atom_name_reply(connection, foo, nullptr);
        
        qDebug() << "\t" << xcb_get_atom_name_name(bar);
        
        free(bar);
    }
    
    return true;
}
//============================================================================

// 添加属性
void xcb_add_prop(bool state, WId window, const char *type, const char *prop)
{
    if (!is_platform_x11())
    {
        return;
    }
    
    // 获取属性和类型的原子
    xcb_atom_t prop_atom = xcb_get_atom(prop);
    xcb_atom_t type_atom = xcb_get_atom(type);
    
    // 如果属性原子或类型原子不存在，则返回
    if (prop_atom == XCB_ATOM_NONE || type_atom == XCB_ATOM_NONE)
    {
        return;
    }
    
    QVector<xcb_atom_t> atoms;
    
    // 获取属性列表
    xcb_get_prop_list(window, type, atoms, XCB_ATOM_ATOM);
    
    int index = atoms.indexOf(prop_atom);
    
    // 如果状态为true且属性不在列表中，则添加属性
    if (state && index == -1)
    {
        atoms.push_back(prop_atom);
    }
    // 如果状态为false且属性在列表中，则移除属性
    else if (!state && index >= 0)
    {
        atoms.remove(index);
    }
    
    xcb_connection_t *connection = x11_connection();
    
    // 更新窗口的属性
    xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window, type_atom, XCB_ATOM_ATOM, 32, atoms.count(), atoms.constData());
    
    // 刷新连接
    xcb_flush(connection);
}


//============================================================================
// 通过X11尝试检测窗口管理器
// 参考：https://specifications.freedesktop.org/wm-spec/1.3/ar01s03.html#idm46018259946000
QString detectWindowManagerX11()
{
    if (!is_platform_x11())
    {
        return "UNKNOWN";
    }

    xcb_connection_t *connection = x11_connection();
    xcb_screen_t *first_screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;

    if (!first_screen)
    {
        ADS_PRINT("未通过XCB找到屏幕。");
        return "UNKNOWN";
    }

    // 获取支持窗口
    xcb_window_t root = first_screen->root;
    xcb_window_t support_win = 0;
    QVector<xcb_window_t> sup_windows;

    xcb_get_prop_list(root, "_NET_SUPPORTING_WM_CHECK", sup_windows, XCB_ATOM_WINDOW);

    if (sup_windows.length() == 0)
    {
        // 这似乎已经不再使用，但是wmctrl也是这样做的，所以让我们保险起见。
        // XCB_ATOM_CARDINAL和XCB_ATOM_WINDOW都可以转换为uint32_t，所以重用sup_windows应该没问题。
        xcb_get_prop_list(root, "_WIN_SUPPORTING_WM_CHECK", sup_windows, XCB_ATOM_CARDINAL);
    }

    if (sup_windows.length() == 0)
    {
        ADS_PRINT("在非EWMH兼容的WM上无法获取支持窗口。");
        return "UNKNOWN";
    }

    support_win = sup_windows[0];
    QString ret = xcb_get_prop_string(support_win, "_NET_WM_NAME");

    if (ret.length() == 0)
    {
        ADS_PRINT("出现空的WM名称。");
        return "UNKNOWN";
    }

    return ret;
}

//============================================================================

// 获取窗口管理器
QString windowManager()
{
    if (_window_manager.length() == 0)
    {
        _window_manager = detectWindowManagerX11();
    }
    return _window_manager;
}
#endif


//============================================================================
// 替换QSplitter中的小部件
void replaceSplitterWidget(QSplitter* Splitter, QWidget* From, QWidget* To)
{
    int index = Splitter->indexOf(From);
    From->setParent(nullptr);
    Splitter->insertWidget(index, To);
}
//============================================================================

// 返回给定的停靠小部件区域的插入参数
CDockInsertParam dockAreaInsertParameters(DockWidgetArea Area)
{
    switch (Area)
    {
        case TopDockWidgetArea:
            return CDockInsertParam(Qt::Vertical, false);
        case RightDockWidgetArea:
            return CDockInsertParam(Qt::Horizontal, true);
        case CenterDockWidgetArea:
        case BottomDockWidgetArea:
            return CDockInsertParam(Qt::Vertical, true);
        case LeftDockWidgetArea:
            return CDockInsertParam(Qt::Horizontal, false);
        default:
            return CDockInsertParam(Qt::Vertical, false);
    }
}
//============================================================================

// 将停靠小部件区域转换为侧边栏位置
SideBarLocation toSideBarLocation(DockWidgetArea Area)
{
    switch (Area)
    {
        case LeftAutoHideArea:
            return SideBarLeft;
        case RightAutoHideArea:
            return SideBarRight;
        case TopAutoHideArea:
            return SideBarTop;
        case BottomAutoHideArea:
            return SideBarBottom;
        default:
            return SideBarNone;
    }
}
//============================================================================

// 检查侧边栏位置是否为水平方向
bool isHorizontalSideBarLocation(SideBarLocation Location)
{
    switch (Location)
    {
        case SideBarTop:
        case SideBarBottom:
            return true;
        case SideBarLeft:
        case SideBarRight:
            return false;
        default:
            return false;
    }
}
//============================================================================

// 检查停靠小部件区域是否为侧边栏区域
bool isSideBarArea(DockWidgetArea Area)
{
    return toSideBarLocation(Area) != SideBarNone;
}
//============================================================================

// 创建具有给定不透明度的透明像素图
QPixmap createTransparentPixmap(const QPixmap& Source, qreal Opacity)
{
    QPixmap TransparentPixmap(Source.size());
    TransparentPixmap.fill(Qt::transparent);
    QPainter p(&TransparentPixmap);
    p.setOpacity(Opacity);
    p.drawPixmap(0, 0, Source);
    return TransparentPixmap;
}
//============================================================================

// 递归隐藏空的父级分割器
void hideEmptyParentSplitters(CDockSplitter* Splitter)
{
    while (Splitter && Splitter->isVisible())
    {
        if (!Splitter->hasVisibleContent())
        {
            Splitter->hide();
        }
        Splitter = internal::findParent<CDockSplitter*>(Splitter);
    }
}
//============================================================================

// 为QAbstractButton设置图标
void setButtonIcon(QAbstractButton* Button, QStyle::StandardPixmap StandarPixmap, ads::eIcon CustomIconId)
{
    // 首先尝试使用自定义图标（如果有）
    QIcon Icon = CDockManager::iconProvider().customIcon(CustomIconId);
    if (!Icon.isNull())
    {
        Button->setIcon(Icon);
        return;
    }

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    Button->setIcon(Button->style()->standardIcon(StandarPixmap));
#else
    // 标准图标在高DPI屏幕上效果不好，因此我们在这里创建自己的“标准”图标。
    QPixmap normalPixmap = Button->style()->standardPixmap(StandarPixmap, 0, Button);
    Icon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), QIcon::Disabled);
    Icon.addPixmap(normalPixmap, QIcon::Normal);
    Button->setIcon(Icon);
#endif
}
//============================================================================

// 重新调整小部件及其子级的样式
void repolishStyle(QWidget* w, eRepolishChildOptions Options)
{
    if (!w)
    {
        return;
    }

    w->style()->unpolish(w);
    w->style()->polish(w);

    if (RepolishIgnoreChildren == Options)
    {
        return;
    }

    QList<QWidget*> Children = w->findChildren<QWidget*>(QString(),
        (RepolishDirectChildren == Options) ? Qt::FindDirectChildrenOnly : Qt::FindChildrenRecursively);

    for (auto Widget : Children)
    {
        Widget->style()->unpolish(Widget);
        Widget->style()->polish(Widget);
    }
}
//============================================================================

// 返回小部件的全局几何位置
QRect globalGeometry(QWidget* w)
{
    QRect g = w->geometry();
    g.moveTopLeft(w->mapToGlobal(QPoint(0, 0)));
    return g;
}

} // namespace internal
} // namespace ads



//---------------------------------------------------------------------------
// EOF ads_globals.cpp
